// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};

use crate::config::{DepsConfig, PkgDepConfig};

#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FeaturesConfig {
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub default: Vec<String>,
    #[serde(default, flatten)]
    pub configs: BTreeMap<String, FeatureConfig>,
}

#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct FeatureConfig {
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub depends: Vec<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub host_depends: Vec<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub runtime_depends: Vec<String>,
}

impl FeaturesConfig {
    pub fn is_empty(&self) -> bool {
        self.configs.is_empty()
    }
}

impl FeatureConfig {
    fn optional_depends_impl<'a>(
        deps: &'a Vec<String>,
        deps_config: &'a DepsConfig,
    ) -> impl Iterator<Item = (&'a PkgDepConfig, &'a String)> {
        deps
            .iter()
            .filter_map(|name| {
                let Some(c) = deps_config.0.get(name.as_str()) else {
                    return None;
                };
                if c.optional {
                    Some((c, name))
                } else {
                    None
                }
            })
    }

    pub fn optional_depends<'a>(&'a self, deps_config: &'a DepsConfig) -> impl Iterator<Item = (&'a PkgDepConfig, &'a String)> {
        Self::optional_depends_impl(&self.depends, deps_config)
    }

    pub fn optional_host_depends<'a>(&'a self, deps_config: &'a DepsConfig) -> impl Iterator<Item = (&'a PkgDepConfig, &'a String)> {
        Self::optional_depends_impl(&self.host_depends, deps_config)
    }

    pub fn optional_runtime_depends<'a>(&'a self, deps_config: &'a DepsConfig) -> impl Iterator<Item = (&'a PkgDepConfig, &'a String)> {
        Self::optional_depends_impl(&self.runtime_depends, deps_config)
    }
}

#[cfg(test)]
mod test {
    use crate::config::pkg_config::{FeatureConfig, FeaturesConfig};

    #[test]
    fn test_feature_config_serialize() {
        assert_eq!(
            toml::to_string_pretty(&FeatureConfig {
                depends: vec!["a".to_string()],
                runtime_depends: vec!["b".to_string(), "c".to_string()],
                host_depends: vec![],
            }),
            Ok("\
                depends = [\"a\"]\n\
                runtime-depends = [\n    \"b\",\n    \"c\",\n]\n".to_string())
        );
    }

    #[test]
    fn test_feature_config_deserialize() {
        assert_eq!(
            toml::from_str("\
                depends = [\"a\"]\n\
                runtime-depends = [\n    \"b\",\n    \"c\",\n]\n"),
            Ok(FeatureConfig {
                depends: vec!["a".to_string()],
                runtime_depends: vec!["b".to_string(), "c".to_string()],
                host_depends: vec![],
            })
        );
    }

    #[test]
    fn test_features_config_serialize() {
        assert_eq!(
            toml::to_string_pretty(&FeaturesConfig {
                default: vec!["f1".to_string(), "f2".to_string()],
                configs: [
                    ("f1".to_string(), FeatureConfig::default()),
                    ("f2".to_string(), FeatureConfig {
                        depends: vec!["a".to_string()],
                        runtime_depends: vec!["b".to_string(), "c".to_string()],
                        host_depends: vec![],
                    })
                ].into_iter().collect()
            }),
            Ok("\
                default = [\n    \"f1\",\n    \"f2\",\n]\n\
                \n\
                [f1]\n\
                \n\
                [f2]\n\
                depends = [\"a\"]\n\
                runtime-depends = [\n    \"b\",\n    \"c\",\n]\n".to_string())
        );
    }

    #[test]
    fn test_features_config_deserialize() {
        assert_eq!(
            toml::from_str("\
                default = [\n    \"f1\",\n    \"f2\",\n]\n\
                \n\
                [f1]\n\
                \n\
                [f2]\n\
                depends = [\"a\"]\n\
                runtime-depends = [\n    \"b\",\n    \"c\",\n]\n"
            ),
            Ok(FeaturesConfig {
                default: vec!["f1".to_string(), "f2".to_string()],
                configs: [
                    ("f1".to_string(), FeatureConfig::default()),
                    ("f2".to_string(), FeatureConfig {
                        depends: vec!["a".to_string()],
                        runtime_depends: vec!["b".to_string(), "c".to_string()],
                        host_depends: vec![],
                    })
                ].into_iter().collect()
            })
        );
    }
}